package synchronized_;

/**
 * @author DCSGO
 * @version 1.0
 * 线程同步机制
 * 1. 在多线程编程中，一些敏感数据不允许被多个线程同时访问，此时就会使用同步访问技
 * 术，保证数据在任何时刻，最多有一个线程访问，以保证数据的完整性。
 * 2. 也可以这里理解: 线程同步，即当有一个线程在对内存进行操作时，其他线程都不
 * 可以对这个内存地址进行操作，直到该线程完成操作，其他线程才能对该内存地址进行操作。
 */
public class Synchronized {
/*
具体使用方法:
1.同步代码块
    synchronized(对象){ //得到对象的锁 才能操作同步代码
        //需要被同步的代码
    }
2.synchronized 还可以放在方法声明中 表示整个方法为同步方法
    public synchronized void m(参数列表){
        //需要被同步的代码
    }
 */

    //通过线程同步解决多线程售票问题
    public static void main(String[] args) {
        SellTick sellTick = new SellTick();
        new Thread(sellTick).start();
        new Thread(sellTick).start();
        new Thread(sellTick).start();
        /*
         * 窗口 Thread-0售出1张票 还剩 19张票
         * 窗口 Thread-0售出1张票 还剩 18张票
         * 窗口 Thread-0售出1张票 还剩 17张票
         * 窗口 Thread-0售出1张票 还剩 16张票
         * 窗口 Thread-0售出1张票 还剩 15张票
         * 窗口 Thread-0售出1张票 还剩 14张票
         * 窗口 Thread-0售出1张票 还剩 13张票
         * 窗口 Thread-0售出1张票 还剩 12张票
         * 窗口 Thread-0售出1张票 还剩 11张票
         * 窗口 Thread-2售出1张票 还剩 10张票
         * 窗口 Thread-2售出1张票 还剩 9张票
         * 窗口 Thread-2售出1张票 还剩 8张票
         * 窗口 Thread-2售出1张票 还剩 7张票
         * 窗口 Thread-2售出1张票 还剩 6张票
         * 窗口 Thread-1售出1张票 还剩 5张票
         * 窗口 Thread-1售出1张票 还剩 4张票
         * 窗口 Thread-1售出1张票 还剩 3张票
         * 窗口 Thread-1售出1张票 还剩 2张票
         * 窗口 Thread-1售出1张票 还剩 1张票
         * 窗口 Thread-1售出1张票 还剩 0张票
         *
         * 进程已结束，退出代码为 0
         */
    }
}

//实现Runnable接口
class SellTick implements Runnable {
    //可以仅实例化一个对象并开启多个线程 多个线程操作的是同一个对象
    //故可不用 static 修饰 达到共享的效果
    private int ticketNum = 20;

    //同步方法
    private synchronized boolean sell() {
        if (ticketNum <= 0) {
            return false;
        }
        System.out.println("窗口 " + Thread.currentThread().getName()
                + "售出1张票 还剩 " + (--ticketNum) + "张票");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }

    //同步代码块
    public boolean sell02() {
        synchronized (this) {
            if (ticketNum == 0) {
                return false;
            }
            System.out.println("窗口 " + Thread.currentThread().getName()
                    + "售出1张票 还剩 " + (--ticketNum) + "张票");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return true;
        }
    }

    @Override
    public void run() {
        while (sell02()) {

        }
    }
}

/*
互斥锁基本介绍
1.Java语言中，引入了对象互斥锁的概念，来保证共享数据操作的完整性。
2.每个对象都对应于一个可称为“互斥锁”的标记，这个标记用来保证在任一时刻，只能有一个线程访问该对象。
3.关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时，表明该对象在任一时刻只能由一个线程访问
4.同步的局限性: 导致程序的执行效率要降低
5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6.同步方法(静态的）的锁为当前类本身。
*/

class T extends Thread {
    static int cnt = 5;
    private static final Object lock02 = new Object();

    public void lowerCnt() {
        cnt--;
        System.out.println(currentThread().getName() + " " + "cnt = " + cnt);
        try {
            sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            //synchronized (lock02) {
            if (cnt <= 0) {
                System.out.println("end... cnt = " + cnt);
                break;
            } else {
                lowerCnt();
            }
        }
        //}
        //在原始的代码中，while循环中没有使用同步代码块，只有在调用lowerCnt()方法时才使用了同步代码块。
        //这样可能会导致多个线程同时访问cnt变量，破坏了线程安全性，从而产生意外的结果。
        //如果多个线程同时检查到cnt大于0，然后同时执行lowerCnt()方法，它们会对cnt进行操作，
        //并可能覆盖或重复修改cnt的值，导致结果不正确。
        //通过在while循环中使用同步代码块，可以确保每次只有一个线程能够执行同步代码块中的逻辑，
        //保持了对cnt变量的互斥访问。这样就能正确地保证线程安全性，避免竞争条件和不一致的结果。
        //修改后的代码中，通过使用同步代码块包裹整个while循环，确保了在每次判断和执行逻辑时，
        //只有一个线程能够进入同步代码块，从而保证了对cnt变量的互斥访问。

        //在任意时刻虽然只能有一个线程能执行同步代码块 但是当我们已经执行到同步代码块所在代码时
        //是否执行它只是时间问题 所以上述代码的根源性错误就在于 互斥锁加错位置了 当然锁对象同样不能是 this
    }

    public static void main(String[] args) {
        T t1 = new T();
        T t2 = new T();
        T t3 = new T();
        t1.start();
        t2.start();
        t3.start();
        /*
         * Thread-0 cnt = 3
         * Thread-2 cnt = 2
         * Thread-1 cnt = 3
         * Thread-1 cnt = 0
         * Thread-0 cnt = 0
         * Thread-2 cnt = 0
         * end... cnt = 0
         * end... cnt = 0
         * end... cnt = 0
         */
        //锁的对象不是同一个 没有真正互斥
    }

    static final Object object = new Object();

    public void m1() {
        synchronized (this) {
            System.out.println("m1");
        }
    }

    public void m3() {
        synchronized (object) {
            System.out.println("m3");
        }
    }

    public static void m2() {
        //synchronized (this) { //编译错误
        synchronized (T.class) {
            System.out.println("m2");
        }
    }
}
/*
注意事项和细节：
1.同步方法如果没有使用static修饰: 默认锁对象为this
2.如果方法使用static修饰,默认锁对象: 当前类.class
3.实现的落地步骤:
  需要先分析上锁的代码
  选择同步代码块或同步方法
  要求多个线程的锁对象为同一个即可!
 */

